package top.lingkang.finaloauth2.server.auth.impl;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import top.lingkang.finaloauth2.constants.ServerConstants;
import top.lingkang.finaloauth2.entity.*;
import top.lingkang.finaloauth2.server.auth.AuthDetailsService;
import top.lingkang.finaloauth2.server.auth.AuthServerGetToken;
import top.lingkang.finaloauth2.server.auth.AuthServerResponse;
import top.lingkang.finaloauth2.server.auth.AuthorizationMode;
import top.lingkang.finaloauth2.server.base.AuthKeyGenerate;
import top.lingkang.finaloauth2.server.client.ClientDetails;
import top.lingkang.finaloauth2.server.client.ClientDetailsService;
import top.lingkang.finaloauth2.server.config.AuthorizationServerProperties;
import top.lingkang.finaloauth2.server.error.AuthServerException;
import top.lingkang.finaloauth2.server.error.ClientNotExistsException;
import top.lingkang.finaloauth2.server.store.TokenStore;
import top.lingkang.finaloauth2.utils.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @author lingkang
 * Created by 2022/3/24
 */
public class AuthorizationModeManage implements AuthorizationMode {
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private AuthDetailsService authDetailsService;
    @Autowired
    private AuthServerGetToken authServerGetToken;
    @Autowired
    private AuthKeyGenerate authKeyGenerate;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private AuthorizationServerProperties properties;
    @Autowired
    private AuthServerResponse authServerResponse;

    @Override
    public Object token(HttpServletRequest request, HttpServletResponse response) {
        String grant_type = request.getParameter("grant_type");
        // 刷新令牌
        if (grant_type.startsWith("r")) {
            return refreshToken(request, response);
        } else if (grant_type.startsWith("p")) { //  密码模式 账号密码登录
            return passwordLogin(request, response);
        } else if (grant_type.startsWith("a")) {// 授权码获取令牌
            return authCode(request, response);
        }
        throw new AuthServerException("无法处理的请求！");
    }

    private Object refreshToken(HttpServletRequest request, HttpServletResponse response) {
        String clientId = request.getParameter("client_id");
        if (clientId == null) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails == null) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        if (!clientDetails.getGrantType().contains("refresh_token")) {
            throw new AuthServerException("client_id 不支持 refresh_token 模式！");
        }

        String clientSecret = request.getParameter("client_secret");
        if (StringUtils.isEmpty(clientSecret) || !clientSecret.equals(clientDetails.getClientSecret())) {
            throw new AuthServerException("无效的 client_secret！");
        }
        String refreshToken = authServerGetToken.getRefreshToken(request);
        if (refreshToken == null) {
            throw new AuthServerException("无效的刷新token, invalid refresh_token");
        }
        OAuth2RefreshToken refresh = tokenStore.getRefreshToken(refreshToken);
        if (refresh == null) {
            throw new AuthServerException("无效的刷新token, invalid refresh_token");
        }
        long current = System.currentTimeMillis();
        // 移除原有token
        tokenStore.removeAccessToken(refresh.getAccessToken().getToken());
        String token = authKeyGenerate.token(request);
        refresh.getAccessToken().setToken(token);
        refresh.getAccessToken().setExpiry(current + properties.getTokenMaxExpires());
        // 更新原有token
        tokenStore.storeRefreshToken(refresh);
        tokenStore.storeAccessToken(refresh.getAccessToken(), refresh.getUserDetails());

        // 返回结果
        return authServerResponse.passwordAuthSuccess(token, refreshToken,
                properties.getTokenMaxExpires() / 1000,
                (refresh.getExpiry() - current) / 1000,
                refresh.getAccessToken().getScope(),
                request, response);
    }

    private Object passwordLogin(HttpServletRequest request, HttpServletResponse response) {
        String clientId = request.getParameter("client_id");
        if (StringUtils.isEmpty(clientId)) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails == null) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        if (!clientDetails.getGrantType().contains("password")) {
            throw new AuthServerException("client_id 不支持 password 模式！");
        }
        String clientSecret = request.getParameter("client_secret");
        if (StringUtils.isEmpty(clientSecret) || !clientSecret.equals(clientDetails.getClientSecret())) {
            throw new AuthServerException("无效的 client_secret");
        }
        // 执行登录
        UserDetails userDetails = authDetailsService.loginUser(
                request.getParameter("username"),
                request.getParameter("password"),
                request);
        if (userDetails == null) {
            throw new AuthServerException("账号不存在或者密码错误！username or password error");
        }
        userDetails.setClientId(clientId);

        String token, refreshToken;
        long tokenExpires = properties.getTokenMaxExpires() / 1000;
        long refreshExpires = properties.getRefreshTokenMaxExpires() / 1000;
        long current = System.currentTimeMillis();
        UserTokenInfo userTokenInfo = tokenStore.getUserTokenInfo(userDetails.getUsername(), clientId);
        if (userTokenInfo != null) {
            if (userTokenInfo.getToken() != null) {
                token = userTokenInfo.getToken();
                tokenExpires = (userTokenInfo.getTokenExpires() - current) / 1000;
            } else {
                token = authKeyGenerate.token(request);
            }
            if (userTokenInfo.getRefreshToken() != null) {
                refreshToken = userTokenInfo.getRefreshToken();
                refreshExpires = (userTokenInfo.getRefreshExpires() - current) / 1000;
            } else {
                refreshToken = authKeyGenerate.refreshToken(request);
            }
        } else {
            token = authKeyGenerate.token(request);
            refreshToken = authKeyGenerate.refreshToken(request);
        }

        OAuth2AccessToken accessToken = new OAuth2AccessToken();
        accessToken.setToken(token);
        accessToken.setExpiry(current + properties.getTokenMaxExpires());
        accessToken.setScope(clientDetails.getScope());
        accessToken.setRefreshToken(refreshToken);
        tokenStore.storeAccessToken(accessToken, userDetails);

        // 构建刷新令牌
        if (userTokenInfo != null && userTokenInfo.getRefreshToken() != null) {
            // 不做操作
        } else {
            OAuth2RefreshToken auth2RefreshToken = new OAuth2RefreshToken();
            auth2RefreshToken.setAccessToken(accessToken);
            auth2RefreshToken.setUserDetails(userDetails);
            auth2RefreshToken.setExpiry(current + properties.getRefreshTokenMaxExpires());
            auth2RefreshToken.setRefreshToken(refreshToken);
            tokenStore.storeRefreshToken(auth2RefreshToken);
        }


        return authServerResponse.passwordAuthSuccess(
                accessToken.getToken(),
                accessToken.getRefreshToken(),
                tokenExpires,
                refreshExpires,
                clientDetails.getScope(),
                request, response);
    }

    private Object authCode(HttpServletRequest request, HttpServletResponse response) {
        String code = request.getParameter("code");
        if (StringUtils.isEmpty(code)) {
            throw new ClientNotExistsException("无效的 code 参数");
        }
        String clientId = request.getParameter("client_id");
        if (StringUtils.isEmpty(clientId)) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails == null) {
            throw new ClientNotExistsException("无效的 client_id");
        }
        String clientSecret = request.getParameter("client_secret");
        if (StringUtils.isEmpty(clientSecret) || !clientSecret.equals(clientDetails.getClientSecret())) {
            throw new AuthServerException("无效的 client_secret");
        }
        Oauth2Code oauth2Code = tokenStore.getOauth2Code(code);
        if (oauth2Code == null) {
            throw new ClientNotExistsException("code 无效或者已经失效");
        }
        if (!clientId.equals(oauth2Code.getClientId())) {
            throw new ClientNotExistsException("client_id 不匹配");
        }
        // 根据授权码的客户端获取对应的角色
        Set<String> role = authDetailsService.codeAuthorizationRole(oauth2Code, clientDetails, request);

        UserTokenInfo userTokenInfo = tokenStore.getUserTokenInfo(ServerConstants.AUTH_CODE_PREFIX + clientId, clientId);
        String token = null;
        long current = System.currentTimeMillis();
        if (userTokenInfo != null && userTokenInfo.getToken() != null) { // 不为空时使用原来的
            token = userTokenInfo.getToken();
        }
        if (token == null)
            token = authKeyGenerate.token(request);

        OAuth2AccessToken accessToken = new OAuth2AccessToken();
        accessToken.setToken(token);
        accessToken.setExpiry(current + properties.getTokenMaxExpires()); // 更新时间
        accessToken.setScope(clientDetails.getScope());
        accessToken.setRefreshToken(null);// 刷新令牌未空！

        // 虚构授权码的用户信息
        UserDetails userDetails = new UserDetails();
        userDetails.setUsername(ServerConstants.AUTH_CODE_PREFIX + clientId);
        userDetails.setRole(role);
        userDetails.setClientId(clientId);
        tokenStore.storeAccessToken(accessToken, userDetails);

        JSONObject res = new JSONObject();
        res.put("code", 0);
        accessToken.setExpiry(properties.getTokenMaxExpires() / 1000);
        res.put("data", accessToken);
        return res.toJSONString();
    }

    @Override
    public Object code(HttpServletRequest request, HttpServletResponse response) {
        String redirect_url = request.getParameter("redirect_url");
        if (StringUtils.isEmpty(redirect_url)) {
            JSONObject error = new JSONObject();
            error.put("code", 1);
            error.put("msg", "无效的 redirect_url");
            return error;
        }
        String clientId = request.getParameter("client_id");
        if (StringUtils.isEmpty(clientId)) {
            JSONObject error = new JSONObject();
            error.put("code", 1);
            error.put("msg", "无效的 client_id");
            return error;
        }
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails == null) {
            JSONObject error = new JSONObject();
            error.put("code", 1);
            error.put("msg", "无效的 client_id");
            return error;
        } else if (!clientDetails.getGrantType().contains("code")) {
            JSONObject error = new JSONObject();
            error.put("code", 1);
            error.put("msg", "client_id 不支持授权码code模式！");
            return error;
        }
        String code = authKeyGenerate.code(request);
        Oauth2Code oauth2Code = new Oauth2Code();
        oauth2Code.setCode(code);
        oauth2Code.setClientId(clientId);
        oauth2Code.setState(request.getParameter("state"));
        oauth2Code.setRedirectUri(redirect_url);
        oauth2Code.setExpiry(System.currentTimeMillis() + properties.getAuthCodeMaxExpires());
        String scope = request.getParameter("scope");
        if (!StringUtils.isEmpty(scope)) {// 作用域
            HashSet<String> scopes = new HashSet<>(Arrays.asList(scope.split(",")));
            oauth2Code.setScope(scopes);
        }
        try {
            tokenStore.setCode(oauth2Code);
        } catch (Exception e) {
            e.printStackTrace();
            JSONObject error = new JSONObject();
            error.put("code", 1);
            error.put("msg", e.getMessage());
            return error;
        }
        JSONObject res = new JSONObject();
        res.put("code", 0);
        res.put("authCode", code);
        res.put("expiry", (oauth2Code.getExpiry() - System.currentTimeMillis()) / 1000);
        return res;
    }
}
